package edu.northwestern.cbits.purple_robot_manager.probes.studies; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import edu.northwestern.cbits.purple_robot_manager.R; import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference; import edu.northwestern.cbits.purple_robot_manager.logging.LogManager; import edu.northwestern.cbits.purple_robot_manager.probes.Probe; import edu.northwestern.cbits.purple_robot_manager.probes.builtin.ContinuousProbe; @SuppressLint("SimpleDateFormat") public class LivewellActivityCountsProbe extends Probe implements SensorEventListener { public static final String NAME = "edu.northwestern.cbits.purple_robot_manager.probes.studies.LivewellActivityCountsProbe"; private static final String ENABLED = "config_probe_livewell_activities_enabled"; private static final boolean DEFAULT_ENABLED = false; private static final String FREQUENCY = "config_probe_livewell_activities_frequency"; public static final String BUNDLE_DURATION = "BUNDLE_DURATION"; public static final String BUNDLE_NUM_SAMPLES = "BUNDLE_NUM_SAMPLES"; private static final String BUNDLE_X_DELTA = "BUNDLE_X_DELTA"; private static final String BUNDLE_Y_DELTA = "BUNDLE_Y_DELTA"; private static final String BUNDLE_Z_DELTA = "BUNDLE_Z_DELTA"; public static final String BUNDLE_ALL_DELTA = "BUNDLE_ALL_DELTA"; private static long BIN_SIZE_DEFAULT = 60000; private double _lastX = 0; private double _lastY = 0; private double _lastZ = 0; private double _xSum = 0; private double _ySum = 0; private double _zSum = 0; private int _lastFrequency = -1; private Context _context = null; private double _lastBinStart = 0; private int _numSamples = 0; @Override public String getPreferenceKey() { return "services_livewell_activities"; } @Override public String probeCategory(Context context) { return context.getString(R.string.probe_studies_category); } @Override public String name(Context context) { return LivewellActivityCountsProbe.NAME; } @Override public String title(Context context) { return context.getString(R.string.title_livewell_activity_counts_probe); } @Override public boolean isEnabled(Context context) { this._context = context.getApplicationContext(); SharedPreferences prefs = ContinuousProbe.getPreferences(context); SensorManager sensors = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); Sensor sensor = sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (super.isEnabled(context)) { if (prefs.getBoolean(LivewellActivityCountsProbe.ENABLED, LivewellActivityCountsProbe.DEFAULT_ENABLED)) { int frequency = Integer.parseInt(prefs.getString(LivewellActivityCountsProbe.FREQUENCY, ContinuousProbe.DEFAULT_FREQUENCY)); if (this._lastFrequency != frequency) { sensors.unregisterListener(this, sensor); if (frequency != SensorManager.SENSOR_DELAY_FASTEST && frequency != SensorManager.SENSOR_DELAY_UI && frequency != SensorManager.SENSOR_DELAY_NORMAL) { frequency = SensorManager.SENSOR_DELAY_GAME; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { sensors.registerListener(this, sensor, frequency, 0); } else { sensors.registerListener(this, sensor, frequency, null); } this._lastFrequency = frequency; } return true; } else { sensors.unregisterListener(this, sensor); this._lastFrequency = -1; } } else { sensors.unregisterListener(this, sensor); this._lastFrequency = -1; } return false; } @Override public PreferenceScreen preferenceScreen(Context context, PreferenceManager manager) { PreferenceScreen screen = super.preferenceScreen(context, manager); screen.setTitle(this.title(context)); screen.setSummary(R.string.summary_livewell_activity_counts_probe_desc); CheckBoxPreference enabled = new CheckBoxPreference(context); enabled.setTitle(R.string.title_enable_probe); enabled.setKey(LivewellActivityCountsProbe.ENABLED); enabled.setDefaultValue(LivewellActivityCountsProbe.DEFAULT_ENABLED); screen.addPreference(enabled); FlexibleListPreference duration = new FlexibleListPreference(context); duration.setKey(LivewellActivityCountsProbe.FREQUENCY); duration.setEntryValues(R.array.probe_continuous_frequency_values); duration.setEntries(R.array.probe_continuous_frequency_labels); duration.setTitle(R.string.probe_frequency_label); duration.setDefaultValue(ContinuousProbe.DEFAULT_FREQUENCY); screen.addPreference(duration); // TODO: Customizable bin sizes? return screen; } @Override public String summary(Context context) { return context.getString(R.string.summary_livewell_activity_counts_probe_desc); } @Override public void onSensorChanged(SensorEvent event) { final double now = (double) System.currentTimeMillis(); synchronized (this) { if (this._lastBinStart == 0) this._lastBinStart = now; else if (now - this._lastBinStart > LivewellActivityCountsProbe.BIN_SIZE_DEFAULT) { Sensor sensor = event.sensor; Bundle data = new Bundle(); Bundle sensorBundle = new Bundle(); sensorBundle.putFloat(ContinuousProbe.SENSOR_MAXIMUM_RANGE, sensor.getMaximumRange()); sensorBundle.putString(ContinuousProbe.SENSOR_NAME, sensor.getName()); sensorBundle.putFloat(ContinuousProbe.SENSOR_POWER, sensor.getPower()); sensorBundle.putFloat(ContinuousProbe.SENSOR_RESOLUTION, sensor.getResolution()); sensorBundle.putInt(ContinuousProbe.SENSOR_TYPE, sensor.getType()); sensorBundle.putString(ContinuousProbe.SENSOR_VENDOR, sensor.getVendor()); sensorBundle.putInt(ContinuousProbe.SENSOR_VERSION, sensor.getVersion()); data.putDouble(Probe.BUNDLE_TIMESTAMP, now / 1000); data.putString(Probe.BUNDLE_PROBE, this.name(this._context)); data.putBundle(ContinuousProbe.BUNDLE_SENSOR, sensorBundle); data.putDouble(LivewellActivityCountsProbe.BUNDLE_X_DELTA, this._xSum); data.putDouble(LivewellActivityCountsProbe.BUNDLE_Y_DELTA, this._ySum); data.putDouble(LivewellActivityCountsProbe.BUNDLE_Z_DELTA, this._zSum); data.putDouble(LivewellActivityCountsProbe.BUNDLE_ALL_DELTA, (this._xSum + this._ySum + this._zSum) / 3); data.putDouble(LivewellActivityCountsProbe.BUNDLE_DURATION, now - this._lastBinStart); data.putDouble(LivewellActivityCountsProbe.BUNDLE_NUM_SAMPLES, this._numSamples); this.transmitData(this._context, data); this._lastBinStart = now; this._xSum = 0; this._ySum = 0; this._zSum = 0; this._numSamples = 0; } this._xSum += Math.abs(event.values[0] - this._lastX); this._ySum += Math.abs(event.values[1] - this._lastY); this._zSum += Math.abs(event.values[2] - this._lastZ); this._lastX = event.values[0]; this._lastY = event.values[1]; this._lastZ = event.values[2]; this._numSamples += 1; } } @Override public String summarizeValue(Context context, Bundle bundle) { double count = bundle.getDouble(LivewellActivityCountsProbe.BUNDLE_ALL_DELTA); double numSamples = bundle.getDouble(LivewellActivityCountsProbe.BUNDLE_NUM_SAMPLES); double duration = bundle.getDouble(LivewellActivityCountsProbe.BUNDLE_DURATION); return String.format(context.getResources().getString(R.string.summary_livewell_pebble_probe), (duration / 1000), numSamples); } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override public void enable(Context context) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(LivewellActivityCountsProbe.ENABLED, true); e.commit(); } @Override public void disable(Context context) { SharedPreferences prefs = Probe.getPreferences(context); Editor e = prefs.edit(); e.putBoolean(LivewellActivityCountsProbe.ENABLED, false); e.commit(); } @Override public JSONObject fetchSettings(Context context) { JSONObject settings = super.fetchSettings(context); try { JSONObject frequency = new JSONObject(); frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG); JSONArray values = new JSONArray(); String[] options = context.getResources().getStringArray(R.array.probe_continuous_frequency_values); for (String option : options) { values.put(Long.parseLong(option)); } frequency.put(Probe.PROBE_VALUES, values); settings.put(Probe.PROBE_FREQUENCY, frequency); // TODO: configurable bin sizes? } catch (JSONException e) { LogManager.getInstance(context).logException(e); } return settings; } }